//@author A0097091Y
//This class supports functionalities for editing tasks

package crossoff.logic;

import java.io.IOException;
import java.util.Arrays;

import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.joda.time.LocalDate;
import org.joda.time.LocalTime;

import crossoff.datetime.DateTimeHandler;
import crossoff.db.History;
import crossoff.db.HistoryType;
import crossoff.db.Storage;
import crossoff.model.GUIobject;
import crossoff.model.Pair;
import crossoff.model.Task;
import crossoff.ui.CrossOffGUI;

public class CommandEditTask {

	private static int cachedTaskIDToEdit;
	private static final String MESSAGE_EDITED = "TASK EDITED";
	private static final String MESSAGE_EDITING = "EDITING TASK";
	private static final String[] timeWords = { "on", "at", "by" };
	private static final LocalTime endOfDay = new LocalTime(23, 59);


	// Function assumes that command being passed in is in a valid edit command format
	/**
	 * 
	 * @param command
	 * @return GUIobject containing updated ArrayList of Task objects
	 * @throws IOException
	 */
	public static GUIobject parseEditCommand(String command) throws IOException {

		String[] commandSplit = command.split("\\s+");
		Pair<DateTime, Integer> newDateTime;
		Pair<LocalTime, Integer> newTime;
		Pair<LocalDate, Integer> newDate;
		DateTime currStartDateTime;
		DateTime currEndDateTime;


		int taskIDToEdit = CrossOffGUI.getTaskID(Integer
				.parseInt(commandSplit[1]));

		Task taskToEdit = Storage.getInstance().getTaskFromID(taskIDToEdit);
		currStartDateTime = taskToEdit.getStart();
		currEndDateTime = taskToEdit.getEnd();

		if (commandSplit.length == 2) {
			return editTask(taskIDToEdit);
		}

		else if (commandSplit[2].toLowerCase().equals("end")) {

			if (commandSplit[3].equalsIgnoreCase("clear")
					&& commandSplit.length == 4) {
				return editEndDate(taskToEdit, null);
			}

			// accomodate end ___, where ___ represents a time word such as "at"
			// or "by"
			if (Arrays.asList(timeWords).contains(commandSplit[3])) {
				newDateTime = DateTimeHandler.getInstance()
						.evaluateDateTimeStr(commandSplit, 4);
				newTime = DateTimeHandler.getInstance().evaluateTimeStr(
						commandSplit, 4);
				newDate = DateTimeHandler.getInstance().evaluateDateStr(
						commandSplit, 4);
			}

			else {
				newDateTime = DateTimeHandler.getInstance()
						.evaluateDateTimeStr(commandSplit, 3);
				newTime = DateTimeHandler.getInstance().evaluateTimeStr(
						commandSplit, 3);
				newDate = DateTimeHandler.getInstance().evaluateDateStr(
						commandSplit, 3);
			}

			if (newDateTime != null && newDateTime.getFirst() != null) {
				return editEndDate(taskToEdit, newDateTime.getFirst());
			}

			else if (newDate != null && newDate.getFirst() != null) {
				return changeEndDate(newDate, currEndDateTime, taskToEdit);
			}

			else if (newTime != null && newTime.getFirst() != null) {
				return changeEndTime(newTime, currStartDateTime,
						currEndDateTime, taskToEdit);
			}

			else {
				return editDescription(taskToEdit, commandSplit);
			}

		}
		// start date/time
		else if (commandSplit[2].toLowerCase().equals("start")) {

			if (commandSplit[3].equalsIgnoreCase("clear")
					&& commandSplit.length == 4) {
				return editStartDate(taskToEdit, null);
			}

			// accomodate end ___, where ___ represents a time word such as "at"
			// or "by"
			if (Arrays.asList(timeWords).contains(commandSplit[3])) {
				newDateTime = DateTimeHandler.getInstance()
						.evaluateDateTimeStr(commandSplit, 4);
				newTime = DateTimeHandler.getInstance().evaluateTimeStr(
						commandSplit, 4);
				newDate = DateTimeHandler.getInstance().evaluateDateStr(
						commandSplit, 4);
			}

			else {
				newDateTime = DateTimeHandler.getInstance()
						.evaluateDateTimeStr(commandSplit, 3);
				newTime = DateTimeHandler.getInstance().evaluateTimeStr(
						commandSplit, 3);
				newDate = DateTimeHandler.getInstance().evaluateDateStr(
						commandSplit, 3);
			}

			if (newDateTime != null && newDateTime.getFirst() != null) {
				return editStartDate(taskToEdit, newDateTime.getFirst());
			}

			else if (newDate != null && newDate.getFirst() != null) {
				return changeStartDate(newDate, currStartDateTime, taskToEdit);
			}

			else if (newTime != null && newTime.getFirst() != null) {
				return changeStartTime(newTime, currStartDateTime,
						currEndDateTime, taskToEdit);
			}

			else {
				return editDescription(taskToEdit, commandSplit);
			}

		}

		// Flexible editing
		else {

			newDateTime = DateTimeHandler.getInstance().evaluateDateTimeStr(
					commandSplit, 2);
			newTime = DateTimeHandler.getInstance().evaluateTimeStr(
					commandSplit, 2);
			newDate = DateTimeHandler.getInstance().evaluateDateStr(
					commandSplit, 2);

			if (newDateTime != null && newDateTime.getFirst() != null) {
				return editDateFlexible(taskToEdit, newDateTime.getFirst());
			}

			else if (newDate != null && newDate.getFirst() != null) {

				return changeDateFlexible(newDate, currStartDateTime,
						currEndDateTime, taskToEdit);

			}

			else if (newTime != null && newTime.getFirst() != null) {
				return changeTimeFlexible(newTime, currStartDateTime,
						currEndDateTime, taskToEdit);
			}

			else {
				return editDescription(taskToEdit, commandSplit);
			}

		}
	}
	
	//Changes start and/or end time based on type of task
	private static GUIobject changeTimeFlexible(
			Pair<LocalTime, Integer> newTime, DateTime currStartDateTime,
			DateTime currEndDateTime, Task taskToEdit) throws IOException {
		DateTime newStartDateTime;
		DateTime newEndDateTime;
		LocalDate tempDate;
		if (currStartDateTime != null) {
			newStartDateTime = currStartDateTime.withHourOfDay(
					newTime.getFirst().getHourOfDay())
					.withMinuteOfHour(
							newTime.getFirst().getMinuteOfHour());
			return editDateFlexible(taskToEdit, newStartDateTime);
		}

		else if (currEndDateTime != null) {
			tempDate = currEndDateTime.toLocalDate();
			newEndDateTime = tempDate.toDateTime(newTime.getFirst());
			return editDateFlexible(taskToEdit, newEndDateTime);
		} else {
			tempDate = DateTimeHandler.getInstance()
					.figureOutCurrentDate(newTime.getFirst());
			newStartDateTime = tempDate.toDateTime(newTime.getFirst());
			return editDateFlexible(taskToEdit, newStartDateTime);
		}
	}
	
	//Changes start and/or end date based on type of task
	private static GUIobject changeDateFlexible(
			Pair<LocalDate, Integer> newDate, DateTime currStartDateTime,
			DateTime currEndDateTime, Task taskToEdit) throws IOException {
		
		DateTime newStartDateTime;
		DateTime newEndDateTime;
		
		if (currStartDateTime != null) {
			newStartDateTime = currStartDateTime
					.withYear(newDate.getFirst().getYear())
					.withMonthOfYear(
							newDate.getFirst().getMonthOfYear())
					.withDayOfMonth(newDate.getFirst().getDayOfMonth());
			return editDateFlexible(taskToEdit, newStartDateTime);
		} else if (currEndDateTime != null) {
			newEndDateTime = currEndDateTime
					.withYear(newDate.getFirst().getYear())
					.withMonthOfYear(
							newDate.getFirst().getMonthOfYear())
					.withDayOfMonth(newDate.getFirst().getDayOfMonth());
			return editDateFlexible(taskToEdit, newEndDateTime);
		} else {
			return editFullDay(taskToEdit, newDate.getFirst());
		}
	}
	
	//Changes start time given a new time
	private static GUIobject changeStartTime(Pair<LocalTime, Integer> newTime,
			DateTime currStartDateTime, DateTime currEndDateTime,
			Task taskToEdit) throws IOException {
		
		DateTime newStartDateTime;
		LocalDate tempDate;
		
		if (currStartDateTime != null) {
			newStartDateTime = currStartDateTime.withHourOfDay(
					newTime.getFirst().getHourOfDay())
					.withMinuteOfHour(
							newTime.getFirst().getMinuteOfHour());
		} else if (currEndDateTime != null) {
			tempDate = currEndDateTime.toLocalDate();
			newStartDateTime = tempDate.toDateTime(newTime.getFirst());
		} else {
			tempDate = DateTimeHandler.getInstance()
					.figureOutCurrentDate(newTime.getFirst());
			newStartDateTime = tempDate.toDateTime(newTime.getFirst());
		}
		return editStartDate(taskToEdit, newStartDateTime);
	}
	
	//Changes start date given a new date
	private static GUIobject changeStartDate(Pair<LocalDate, Integer> newDate,
			DateTime currStartDateTime, Task taskToEdit) throws IOException {
		
		DateTime newStartDateTime;
		
		if (currStartDateTime != null) {
			newStartDateTime = currStartDateTime
					.withYear(newDate.getFirst().getYear())
					.withMonthOfYear(
							newDate.getFirst().getMonthOfYear())
					.withDayOfMonth(newDate.getFirst().getDayOfMonth());
		} else {
			newStartDateTime = newDate.getFirst()
					.toDateTimeAtStartOfDay();
		}
		return editStartDate(taskToEdit, newStartDateTime);
	}
	
	
	//Changes end time given a new time
	private static GUIobject changeEndTime(Pair<LocalTime, Integer> newTime,
			DateTime currStartDateTime, DateTime currEndDateTime,
			Task taskToEdit) throws IOException {
		
		DateTime newEndDateTime;
		LocalDate tempDate;
		
		if (currEndDateTime != null) {
			newEndDateTime = currEndDateTime.withHourOfDay(
					newTime.getFirst().getHourOfDay())
					.withMinuteOfHour(
							newTime.getFirst().getMinuteOfHour());
		} else if (currStartDateTime != null) {
			tempDate = currStartDateTime.toLocalDate();
			newEndDateTime = tempDate.toDateTime(newTime.getFirst());
		} else {
			tempDate = DateTimeHandler.getInstance()
					.figureOutCurrentDate(newTime.getFirst());
			newEndDateTime = tempDate.toDateTime(newTime.getFirst());
		}
		return editEndDate(taskToEdit, newEndDateTime);
	}
	
	//Changes end date given a new date
	private static GUIobject changeEndDate(Pair<LocalDate, Integer> newDate,
			DateTime currEndDateTime, Task taskToEdit) throws IOException {
		
		DateTime newEndDateTime;
		
		if (currEndDateTime != null) {
			newEndDateTime = currEndDateTime
					.withYear(newDate.getFirst().getYear())
					.withMonthOfYear(
							newDate.getFirst().getMonthOfYear())
					.withDayOfMonth(newDate.getFirst().getDayOfMonth());
		} else {
			newEndDateTime = newDate.getFirst().toDateTime(endOfDay);
		}
		return editEndDate(taskToEdit, newEndDateTime);
	}
	
	//Executes the editing of the start date
	private static GUIobject editStartDate(Task taskToEdit, DateTime newDateTime)
			throws IOException {

		Task oldTask = taskToEdit.makeCopy();
		Task newTask;

		Storage.getInstance().deleteTask(taskToEdit);

		taskToEdit.setStart(newDateTime);
		newTask = taskToEdit.makeCopy();

		Storage.getInstance().addTaskToFile(taskToEdit);
		saveUndo(oldTask, newTask);

		return new GUIobject(MESSAGE_EDITED, "");
	}
	
	
	//Executes the editing of the start/end dates to a full day
	private static GUIobject editFullDay(Task taskToEdit, LocalDate newLocalDate)
			throws IOException {

		Task oldTask = taskToEdit.makeCopy();
		Task newTask;

		Storage.getInstance().deleteTask(taskToEdit);
		LocalTime endOfDay = new LocalTime(23, 59);

		taskToEdit.setStart(newLocalDate.toDateTimeAtStartOfDay());
		taskToEdit.setEnd(newLocalDate.toDateTime(endOfDay));
		newTask = taskToEdit.makeCopy();

		Storage.getInstance().addTaskToFile(taskToEdit);
		saveUndo(oldTask, newTask);

		return new GUIobject(MESSAGE_EDITED, "");
	}
	
	//Executes the editing of start and/or end date depending on the type of task
	private static GUIobject editDateFlexible(Task taskToEdit,
			DateTime newDateTime) throws IOException {

		Task oldTask = taskToEdit.makeCopy();
		Task newTask;

		Storage.getInstance().deleteTask(taskToEdit);

		DateTime startDate = taskToEdit.getStart();
		DateTime endDate = taskToEdit.getEnd();

		// "From" timed tasks
		if (startDate != null && endDate != null) {

			Duration duration = new Duration(taskToEdit.getStart(),
					taskToEdit.getEnd());

			taskToEdit.setStart(newDateTime);
			taskToEdit.setEnd(newDateTime.plusMinutes((int) duration
					.getStandardMinutes()));

		}

		// Deadline tasks
		else if (endDate != null) {
			taskToEdit.setEnd(newDateTime);
		}

		// "at" timed and floating tasks
		else {

			taskToEdit.setStart(newDateTime);
		}
		
		newTask = taskToEdit.makeCopy();
		Storage.getInstance().addTaskToFile(taskToEdit);
		saveUndo(oldTask, newTask);

		return new GUIobject(MESSAGE_EDITED, "");
	}
	
	//Executes the editing of end date
	private static GUIobject editEndDate(Task taskToEdit, DateTime newDateTime)
			throws IOException {

		Task oldTask = taskToEdit.makeCopy();
		Task newTask;
		//oldTask.setTaskID(taskToEdit.getTaskID());
		Storage.getInstance().deleteTask(taskToEdit);

		taskToEdit.setEnd(newDateTime);
		newTask = taskToEdit.makeCopy();
		
		Storage.getInstance().addTaskToFile(taskToEdit);
		saveUndo(oldTask, newTask);

		return new GUIobject(MESSAGE_EDITED, "");

	}
	
	//Executes the editing of task description
	private static GUIobject editDescription(Task taskToEdit,
			String[] commandSplit) throws IOException {

		Task oldTask = taskToEdit.makeCopy();
		Task newTask;
		//oldTask.setTaskID(taskToEdit.getTaskID());
		Storage.getInstance().deleteTask(taskToEdit);

		StringBuilder builder = new StringBuilder();

		for (int i = 2; i < commandSplit.length; i++) {
			builder.append(commandSplit[i]);
			if (i != commandSplit.length - 1) {
				builder.append(" ");
			}
		}

		String newDescription = builder.toString();

		taskToEdit.setDescription(newDescription);
		newTask = taskToEdit.makeCopy();
		Storage.getInstance().addTaskToFile(taskToEdit);
		saveUndo(oldTask, newTask);

		return new GUIobject(MESSAGE_EDITED, "");
	}
	
	//Save editing action into undo stack
	private static void saveUndo(Task oldTask, Task newTask) {
		
		History.getInstance().addToUndo(newTask, HistoryType.EDITED, oldTask);

	}
	
	/**
	 * 
	 * @param taskIDToEdit
	 * @return A GUIobject with the full edit string for the user to edit
	 * @throws IOException
	 */
	private static GUIobject editTask(int taskIDToEdit) throws IOException {

		String textToEdit = Storage.getInstance().getEditDescription(taskIDToEdit);
		cachedTaskIDToEdit = taskIDToEdit;

		try {
			if (textToEdit != null) {
				String logMessage = MESSAGE_EDITING;
				try {
					return new GUIobject(Storage.getInstance().taskCache,
							logMessage, textToEdit, "EDIT");

				}

				catch (IllegalArgumentException e) { // cannot find task
					System.err.println(e.getMessage());
				}
			}
		} catch (IllegalArgumentException e) { // cannot find task
			System.err.println(e.getMessage());
		}

		return null;

	}
	
	/**
	 * Called by CommandLogic during second step of full edit command
	 * @param command
	 * @return GUIobject with updated ArrayList of Task objects.
	 * @throws IOException
	 */
	public static GUIobject executeEditTask(String command) throws IOException {

		Task oldTask = Storage.getInstance().getTaskFromID(cachedTaskIDToEdit);
		Storage.getInstance().deleteTask(oldTask);
		System.out.println("SIGH");

		GUIobject toReturn = CommandAddTask.addTask(command, true);
		toReturn.setFeedback(MESSAGE_EDITED);
		Task newTask = toReturn.getDisplayCache().get(
				toReturn.getDisplayCache().size() - 1);

		History.getInstance().removeLastFromUndo();

		History.getInstance().addToUndo(newTask, HistoryType.EDITED, oldTask);

		return toReturn;
	}
}
